﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Mail;
using System.Collections.Specialized;
using System.Net.Sockets;
using System.Net;
using System.Threading;

////////////
/// 
/// Duplicated to System.Net.Mail
/// By marstone@marstone.net, 2008/12/02
/// 
////////////
namespace Studio.Network.MailServer
{

    /// <summary>
    /// Sample from wikipedia
    /// http://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol
    /// 
    /// S: 220 smtp.example.com ESMTP Postfix
    /// C:HELO relay.example.org
    /// S:250 Hello relay.example.org, I am glad to meet you
    /// C:MAIL FROM:<bob@example.org>
    /// S:250 Ok
    /// C:RCPT TO:<alice@example.com>
    /// S:250 Ok
    /// C:RCPT TO:<theboss@example.com>
    /// S:250 Ok
    /// C:DATA
    /// S:354 End data with <CR><LF>.<CR><LF>
    /// C:From: "Bob Example" <bob@example.org>
    /// C:To: Alice Example <alice@example.com>
    /// C:Cc: theboss@example.com
    /// C:Date: Tue, 15 Jan 2008 16:02:43 -0500
    /// C:Subject: Test message
    /// C:
    /// C:Hello Alice.
    /// C:This is a test message with 5 headers and 4 lines in the body.
    /// C:Your friend,
    /// C:Bob
    /// C:.
    /// S:250 Ok: queued as 12345
    /// C:QUIT
    /// S:221 Bye
    /// {The server closes the connection}
    /// </summary>
    public class SmtpServer
    {

        TcpListener _listener;

        volatile SmtpServerSettings _settings;

        public SmtpServerSettings Settings
        {
            get { return _settings; }
        }

        public SmtpServer(SmtpServerSettings settings)
        {
            // Use default settings if no settings were given
            this._settings = (null == settings) ? new SmtpServerSettings() : settings;

            try
            {
                // Start listener
                this._listener = new TcpListener(IPAddress.Any, _settings.SMTP_PORT);
                this._listener.Start();
                // Initialize with the maximium capacity
                _threadPool = new List<Thread>(_settings.MAX_THREADS_IN_POOL);
            }
            catch (Exception ex)
            {
                Console.WriteLine("[SmtpServer]Exception:{0}", ex.Message);
            }
            finally
            {
                if (null != this._listener) this._listener.Stop();
            }
        }

        public void Start()
        {
            Thread motherThread = new Thread(new ThreadStart(Listen));
            motherThread.Start();
        }

        public void Listen()
        {
            try
            {
                Console.WriteLine("[SmtpServer][Listen]Begin Accept Socket.");

                // Get the semaphore or die directly
                if (false == _serviceState.WaitOne(0)) return;

                this._listener.Start();
                this.State = ServiceState.Running;

                // Main Smtp Service Loop
                while (ServiceState.Running == this.State)
                {
                    _clientConnected.Reset();
                    this._listener.BeginAcceptSocket(new AsyncCallback(ListenerCallback), this._listener);
                    _clientConnected.WaitOne();
                }
                // Wait all thread complete
                lock (_threadLock)
                {
                    foreach (Thread thread in _threadPool)
                        thread.Join();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("[SmtpServer][Listen]Exception:{0}", ex.Message);
            }
            finally
            {
                if (null != this._listener) this._listener.Stop();
                this.State = ServiceState.Stoped;
                _serviceState.Release();
            }
        }

        IList<Thread> _threadPool;

        void ListenerCallback(IAsyncResult ar)
        {
            TcpListener listener = (TcpListener)ar.AsyncState;
            try
            {
                // Get the listener that handles the client request.
                Socket socket = listener.EndAcceptSocket(ar);
                SmtpWorder worker = new SmtpWorder(socket, this);
                // Operate thread pool
                lock (_threadLock)
                {
                    // Remove finished thread
                    for (int i = 0; i < _threadPool.Count; i++ )
                        if (_threadPool[i].ThreadState == ThreadState.Stopped)
                            _threadPool.RemoveAt(i--);
                    // Bypass overloaded request directly
                    if (_settings.MAX_THREADS_IN_POOL > _threadPool.Count)
                    {
                        Thread thread = new Thread(new ThreadStart(worker.Serve));
                        _threadPool.Add(thread);
                        thread.Start();
                    }
                }
            }
            catch (ObjectDisposedException odex)
            { 
                // Do nothing. It would occur when TcpListener.Close is called.
                System.Diagnostics.Debug.WriteLine(odex.StackTrace);
            }
            catch (Exception ex)
            {
                Console.WriteLine("[SmtpServer][Listen][Callback]Exception:{0}", ex.Message);
            }
            finally
            {
                _clientConnected.Set();
            }
        }

        /// <summary>
        /// This SHOULD be called when every thread complete.
        /// </summary>
        public void ThreadJoin(Thread thread)
        {
            int index = _threadPool.IndexOf(thread);
            if (0 <= index)
            {
                thread.Join();
                _threadPool.Remove(thread);
            }
        }

        /// <summary>
        /// Metux object
        /// </summary>
        public static object _stateLock = new object();

        public static object _threadLock = new object();

        /// <summary>
        /// Resource : Server 
        /// </summary>
        Semaphore _serviceState = new Semaphore(1, 1);

        /// <summary>
        /// Event when client connected
        /// </summary>
        ManualResetEvent _clientConnected = new ManualResetEvent(false);

        private ServiceState _state = ServiceState.Stoped;
        /// <summary>
        /// Service State, to control/indicate the whole service workflow
        /// </summary>
        public ServiceState State
        {
            get { return _state; }
            set 
            {
                lock (_stateLock)
                {
                    _state = value;
                }
            }
        }

        public void Stop()
        {
            ServiceState state = this.State;
            switch (state)
            {
                case ServiceState.Stopping:
                case ServiceState.Running:
                    this.State = ServiceState.Stopping;
                    // Trigger a fake connected event to wake the _listener
                    _clientConnected.Set();
                    _serviceState.WaitOne();
                    // Wake the next Object in the queue
                    _serviceState.Release();
                    break;
                case ServiceState.Stoped:
                    return;
            }
        }
    }
}
